package org.jeeasy.common.core.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.IOException;
import java.time.*;
import java.util.TimeZone;

/**
 * 时间格式化
 *
 * @author AlpsDDJ
 * @date 2020/11/10
 */

@Configuration
public class DateformatConfig {

    @Bean
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        //反序列化的时候如果多了其他属性,不抛出异常
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        SimpleModule module = new SimpleModule();
        //序列化 :序列换成json时,将所有的long变成string(防丢精度)
        //module.addSerializer(Long.class, ToStringSerializer.instance);
        //module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        module.addSerializer(LocalDateTime.class, new DateformatConfig.LocalDateTimeSerializer());
        module.addSerializer(LocalDate.class, new DateformatConfig.LocalDateSerializer());
        module.addSerializer(LocalTime.class, new DateformatConfig.LocalTimeSerializer());

        module.addDeserializer(LocalDateTime.class, new DateformatConfig.LocalDateTimeDeserializer());
        module.addDeserializer(LocalDate.class, new DateformatConfig.LocalDateDeserializer());
        module.addDeserializer(LocalTime.class, new DateformatConfig.LocalTimeDeserializer());
        objectMapper.registerModule(module);
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);

        return jackson2HttpMessageConverter;
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            // 这一部分也可以在 yaml 中配置
            builder
                    // 序列化时，对象为 null，是否抛异常
                    .failOnEmptyBeans(false)
                    // 反序列化时，json 中包含 pojo 不存在属性时，是否抛异常
                    .failOnUnknownProperties(false)
                    // 禁止将 java.util.Date, Calendar 序列化为数字(时间戳)
                    //.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                    // 设置 java.util.Date, Calendar 序列化、反序列化的格式
                    //.dateFormat(new SimpleDateFormat(DATETIME_FORMAT))
                    // 设置 java.util.Date、Calendar 序列化、反序列化的时区
                    .timeZone(TimeZone.getTimeZone("GMT+8"))
            ;

            // Jackson 序列化 long类型为String，解决后端返回的Long类型在前端精度丢失的问题
            //builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
            //builder.serializerByType(Long.class, ToStringSerializer.instance);
            //builder.serializerByType(Long.TYPE, ToStringSerializer.instance);

            builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer());
            builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer());

            builder.serializerByType(LocalDate.class, new LocalDateSerializer());
            builder.deserializerByType(LocalDate.class, new LocalDateDeserializer());

            builder.serializerByType(LocalTime.class, new LocalTimeSerializer());
            builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer());

        };
    }

    private static final ZoneId timezone = ZoneOffset.ofHours(8);

    public static class LocalTimeSerializer extends JsonSerializer<LocalTime> {
        @Override
        public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null) {
                gen.writeNumber(value.toNanoOfDay() / 1000000L - 8 * 60 * 60 * 1000L);
            }
        }
    }

    public static class LocalTimeDeserializer extends JsonDeserializer<LocalTime> {
        @Override
        public LocalTime deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
            long timestamp = parser.getValueAsLong();
            long nanoOfDay = (timestamp + 8 * 60 * 60 * 1000L) * 1000000L;
            return LocalTime.ofNanoOfDay(nanoOfDay);
        }
    }

    public static class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null) {
                gen.writeNumber(value.atStartOfDay(timezone).toInstant().toEpochMilli());
            }
        }
    }

    public static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
        @Override
        public LocalDate deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
            long timestamp = parser.getValueAsLong();
            return timestamp < 0 ? null : LocalDate.ofInstant(Instant.ofEpochMilli(timestamp), timezone);
        }
    }

    public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null) {
                gen.writeNumber(value.atZone(timezone).toInstant().toEpochMilli());
            }
        }
    }

    public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
            long timestamp = parser.getValueAsLong();
            return timestamp < 0 ? null : LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), timezone);
        }
    }

}
